########################################
Google 风格指南
########################################
本文是 `Google 开源项目风格指南 `_ 的简要小结,但是小部分加入了一些我的注释
头文件
****************************************
#. 除了 **main()** 函数和单元测试文件外,每个源文件都应当有对应的头文件
#. 头文件应该是 :abbr:`自包含 (Self-contained)` 的,所谓 **自包含** 是指外部引用时不需要为特殊场合包含额外的头文件
#. 内联函数和模板应当在头文件中添加定义而不是在源文件中
.. note::
- 对于 MSVC 而言,内联函数必须在头文件中实现,否则会编译失败
- 如果某函数模板为所有相关模板参数显式实例化,或是某个类的私有成员,那么它就只能在源文件中实现
#. 头文件应当有 **#define** 或 **#prgma once** 的保护,以防止头文件被多次包含
.. tip::
**#define** 的命名格式为 ``___H_`` ,其中 **** 相对于项目的源代码路径
#. 除了本项目的实体外,函数、类模板等应当避免使用 `前置声明 `_
.. note::
使用前置声明引入的类是不完全类型,我们只能
- 使用类的指针或引用
- **声明** 以不完全类型作为参数或返回值的函数
不完全类型不能用来创建对象或引用其函数。因此一般在头文件中使用前置声明
不要对标准库中的类使用前置类型声明
#. 只有函数代码少于十行时才会被定义为内联函数 ( *析构函数* 、 *递归函数* 和 *虚函数* 不应当定义为内联函数)
#. **#include** 应当按照以下顺序包含头文件:
#. 源文件对应的头文件
#. C 系统头文件
#. C++ 系统头文件
#. 其他库中的头文件
#. 本项目中的头文件
#. 按条件包含的头文件
在每个类别中应当插入空行进行分隔
作用域
****************************************
#. 命名空间
- 在 **源文件** 中建议使用 *匿名命名空间* 或 *static 声明* 。但是头文件不允许
- 具名的命名空间其名字应当基于项目名或相对路径
- 禁止使用 *using* 和 *内敛命名空间*
- 禁止在头文件中使用 *命名空间别名*
- 命名空间别名应当只在实现中使用
#. 函数和变量
- 非成员函数应当总是放在命名空间中
- 不要使用类的静态函数模拟命名空间的效果
- 类的静态方法应当只和类的实例或静态数据相关
- 变量的作用域应当尽量缩小。只在需要使用变量时才声明它
- 应当使用初始化的方式声明变量而不是先声明再赋值(当在循环中使用对象时例外)
- 禁止创建非 `POD `_ 类型的 static 变量和全局变量 ( *constexpr* 变量除外)
- 禁止使用含有副作用的函数初始化 POD 全局变量
- 禁止使用函数返回值初始化 POD 变量(除非函数返回值不涉及任何全局变量)
.. note::
上述三条主要是防止在多编译单元中变量初始化顺序的不确定性。但在同一个编译单元内,静态初始化优先于动态初始化,初始化顺序与声明顺序相同。
- 为了改善静态变量和全局对象析构的不确定性,可以使用 **quick_exit()** 代替 **exit()** 终止程序,其不会执行析构过程。
类
****************************************
#. 不要在构造函数中以任何形式(直接调用或通过 init() 间接调用)调用虚函数
#. 不要在构造函数中执行过多的逻辑相关的初始化
#. 编译器提供的默认构造函数不会对变量进行初始化
#. 不要在无法报告错误的情况下在 **构造函数** 中调用可能会出错的函数
#. 构造函数的地址无法被获得
#. 构造函数中不得报告非致命错误
#. 不要定义隐式类型转换。转换运算符和单参数构造函数应当使用 *explicit* 修饰
.. note::
如果单参数构造函数没有使用 *explicit* 那么就无法判断这个函数是用作类型转换还是创建对象的。
#. 如果不需要类支持拷贝和移动,就把它们删掉。
这里请参阅 `可拷贝类型和可移动类型 `_
#. 子类重载虚函数时也应该加上 virtual 关键字
#. 只有仿函数和在仅有数据成员时使用结构体,否则使用类
#. 除非是公有继承,否则一律使用组合
#. 当出现多重继承时,最多只能有一个基类是具体类,其他基类必须都为接口类。(除了 `部分情况 `_ )
.. note::
所谓接口类,就是只有纯虚函数和静态函数,没有非静态数据成员,没有定义任何带参数的、非 *protect* 的构造函数。
接口类建议使用 *Interface* 结尾。
#. 尽量不要重载运算符,也不要创建用户自定义字面量
#. 没有副作用的二元运算符不应当定义为成员函数
#. 不要重载 **&&** 、 **||** 、 **,** 和 **&** 。也不要重载 **operator""**
#. 除了 **static const** 类型外,所有数据成员声明为 **private**
#. 控制域应当遵循 public -> protected -> private,将相似的声明放在最后
#. 存取函数一般内联在头文件中。
.. hint::
所谓存取函数,是指存取数据成员的 getter 和 setter 。而不是容器的 push 和 pop
函数
****************************************
- 在函数声明时,输入参数(通常由 const 修饰)要放到输出参数(通常是非 const 指针)前面
- 函数体的长度一般不应该大于 40 行
- 所有按引用传递的参数必须加上 const
- 若要使用函数重载, 应当让用户一看函数签名就知道怎么用, 而不是花心思猜测调用的重载函数到底是哪一种。这一规则也适用于构造函数
.. note::
如果函数的参数数量相同,与其重载一个函数(例如 Append() ),不如在函数名上加上参数信息。(比如 AppendString、AppendInt 等)
#. 禁止在纯虚函数中使用默认参数。在函数重载时 **必须** 保证默认参数的值相同。
.. note::
对于默认参数而言,以下需要注意:
- 默认参数时函数重载的另一种语义,一般情况下使用函数重载而不是默认参数
- 默认参数在每个调用点都需要进行重新求值,这会导致生成的代码迅速膨胀
- 缺省参数会干扰函数指针, `导致函数签名与调用点的签名不一致 `_ 。 而函数重载不会导致这样的问题
- 如果使用函数指针调用带有默认参数的函数,那么默认参数无效。这是因为默认参数是在运行时求值,而函数调用是编译时。
- 默认参数值一旦改变,所有设计到函数的文件都必须重新编译
- 只为可以缺省的参数提供默认值,不要为了调用时省劲就用默认参数 [#]_
除以下情况外 **非常不建议** 使用默认参数: [#]_
- 源文件中的静态函数或者匿名空间函数
- 构造函数
- 用来模拟变长数组
例如:
.. code-block:: cpp
// 通过空 AlphaNum 以支持四个形参
string StrCat(const AlphaNum &a,
const AlphaNum &b = gEmptyAlphaNum,
const AlphaNum &c = gEmptyAlphaNum,
const AlphaNum &d = gEmptyAlphaNum);
#. 只有在不方便使用前置类型声明时采用后置类型声明
.. note::
C++ 的前置类型声明的方法是:
.. code-block:: C++
int foo(int x);
后置类型写法是:
.. code-block:: C++
auto foo(int x) -> int;
只有在前置类型声明不方便的时候才使用后置类型声明(比如 Lambda 表达式)
#. 只有在移动构造和移动赋值时才使用右值引用。不要使用 **std::forward**
#. 不允许使用变长数组和 **alloca()**
#. 不允许使用 C 风格的类型转换
#. 只在记录日志时使用流,其他时候使用 *printf* 和 *read*
.. note::
事实上,流使我们在打印时不在关心对象的类型,但是这也导致用错代码时编译器不会有任何警告。而且流不做特殊处理的时候效率是很低的。尽管我们可以一边用流一边用 printf ,但是根据 **一致性原则** ,我们除了日志接口外,其余部分都用 *printf* 和 *read* 以统一 IO 接口。
#. 对于迭代器和其他模板对象而言,只使用前置自增
#. 尽可能使用 **const** 和 **constexpr** 。 const 建议放在类型前面。
#. 在声明 const 对象的同时对对象进行初始化。
#. 关键字 **mutable** 是线程不安全的,谨慎使用、
#. 对于整型而言,只使用 **int** 、 **int8_t** 、 **int64_t** 等。(位于 *cstdint* )
#. 64 位下的可移植性
.. note::
此部分主要针对打印,参见: `64 位下的可移植性 `_
#. 尽量使用内联函数、枚举和常量代替宏
- 不要在头文件中定义宏
- 只有在马上使用时才 **#define** ,之后立即 **#undef**
- 不要对一个已经存在的宏使用 **#undef** ,选择一个不会冲突的名称
- 不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为
- 不要用 **##** 处理函数,类和变量的名字
#. 对于空值而言:整数用 *0* ,实数用 *0.0* ,指针用 *nullptr* 或 *NULL* 、字符用 *'\\0'*
#. 尽量使用 **sizeof(变量名)** 而不是 **sizeof(类型名)**
#. 在保证可读性良好的情况下使用 **auto**
- auto 只能用在局部变量里用。别用在文件作用域变量,命名空间作用域变量和类数据成员里。永远别列表初始化 auto 变量。
#. 使用列表初始化或双括号以避免歧义。使用列表初始化而不是圆括号以避免隐式类型转换。
#. Lambda 不要捕获全部,只捕获需要的部分
#. 不要使用过于复杂的模板编程
#. Boost 只使用以下库:
- Call Traits : *boost/call_traits.hpp*
- Compressed Pair : *boost/compressed_pair.hpp*
- `_
其他内容
****************************************
以下内容是 Google 为了兼容已有代码或兼容社区成员而考虑的,请酌情考虑是否使用:
#. 不允许使用异常
#. 不允许使用 RTTI
.. seealso::
- `对使用 C++ 异常处理应具有怎样的态度? `_
- `Should the trailing return type syntax style become the default for new C++11 programs? `_
.. [#] `Google C++ Style Guide 中为什么禁止使用缺省函数参数? `_
.. [#] `缺省参数 `_
.. [#] `命名规则的特例 `_